Skip to content

perf: avoid await import for code that's guaranteed to execute#8086

Closed
Wodann wants to merge 4 commits intomainfrom
perf/imports
Closed

perf: avoid await import for code that's guaranteed to execute#8086
Wodann wants to merge 4 commits intomainfrom
perf/imports

Conversation

@Wodann
Copy link
Copy Markdown
Member

@Wodann Wodann commented Mar 25, 2026

  • Because this PR includes a bug fix, relevant tests have been included.
  • Because this PR includes a new feature, the change was previously discussed on an Issue or with someone from the team.
  • I didn't do anything of this.

Performance optimisations for Hardhat 3 by moving hot code await imports that are guaranteed to be run to be synchronous.

In the OpenZeppelin contracts repo, this reduced the Mocha test suite runtime from 284.57s to 136.92s (-51.9%).

Copilot AI review requested due to automatic review settings March 25, 2026 16:28
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 25, 2026

⚠️ No Changeset found

Latest commit: 242cea9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Wodann Wodann changed the title Perf/imports perf: avoid await import for code that's guaranteed to execute Mar 25, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Performance-focused refactor aimed at reducing runtime overhead in Hardhat 3 by replacing hot-path dynamic imports/async cloning with synchronous equivalents.

Changes:

  • Made deepClone synchronous by removing lazy await import("rfdc") and updating call sites/tests accordingly.
  • Replaced an await import(...) of createHandlersArray with a static import in the network hook handler.
  • Updated a few internal consumers to stop awaiting deepClone.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compilation-job.ts Stops awaiting deepClone when building compiler input settings.
v-next/hardhat/src/internal/builtin-plugins/network-manager/hook-handlers/network.ts Converts a per-request dynamic import to a static import; stops awaiting deepClone.
v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts Stops awaiting deepClone when copying default chain descriptors.
v-next/hardhat-utils/test/lang.ts Updates deepClone tests to call the new synchronous API.
v-next/hardhat-utils/src/lang.ts Changes deepClone from async to sync (public API surface).
v-next/hardhat-utils/src/internal/lang.ts Switches rfdc to a static import and initializes the clone function eagerly.

Comment on lines +1 to 8
import rfdc from "rfdc";

import { isObject } from "../lang.js";

let clone: ReturnType<typeof rfdcT> | null = null;
export async function getDeepCloneFunction(): Promise<<T>(input: T) => T> {
const { default: rfdc } = await import("rfdc");

if (clone === null) {
clone = rfdc();
}
const clone = rfdc();

export function getDeepCloneFunction(): <T>(input: T) => T {
return clone;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDeepCloneFunction now eagerly imports rfdc and initializes clone at module load (const clone = rfdc()). Because @nomicfoundation/hardhat-utils/lang is imported widely for unrelated helpers (e.g. isObject, sleep), this change makes every consumer pay the rfdc import/init cost even when deepClone is never called, which can regress startup performance and changes when failures would surface (module-load vs first-call).

Consider restoring lazy initialization while keeping deepClone synchronous, e.g. cache clone but only require("rfdc")/initialize it inside getDeepCloneFunction (using createRequire in ESM), or otherwise ensure this eager load is actually on a guaranteed-hot path.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to 15
export function deepClone<T>(value: T): T {
return getDeepCloneFunction()<T>(value);
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing deepClone from async (returning Promise<T>) to a synchronous function (returning T) is a breaking public API change for @nomicfoundation/hardhat-utils/lang. Any external consumers using promise chaining (e.g. deepClone(x).then(...)) or typed as Promise will break.

If the sync behavior is required for perf, consider introducing a new sync API (and keeping the existing async deepClone as a thin wrapper for backwards compatibility), or ensure this is released with an appropriate major-version bump and explicit migration notes.

Copilot uses AI. Check for mistakes.
@Wodann Wodann marked this pull request as ready for review March 25, 2026 17:31
@Wodann Wodann requested review from alcuadrado, Copilot and kanej March 25, 2026 17:31
@Wodann Wodann self-assigned this Mar 25, 2026
@Wodann Wodann added no docs needed This PR doesn't require links to documentation no changeset needed This PR doesn't require a changeset labels Mar 25, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines 6 to 8
import type { HardhatEthersProvider } from "../hardhat-ethers-provider/hardhat-ethers-provider.js";
import type { HardhatEthersSigner } from "../signers/signers.js";
import { HardhatEthersSigner } from "../signers/signers.js";
import type { ethers as EthersT } from "ethers";
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from a dynamic import to a static import here means the signer implementation (and its transitive deps like ethers) will be loaded as soon as this module is evaluated, even in flows that never call getSigner/getSigners. If the goal is to remove per-call await import(...) overhead but keep lazy loading, consider caching the imported module/class the first time getSigner is called (lazy + cached) rather than importing it unconditionally at top-level.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 26, 2026 15:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@alcuadrado
Copy link
Copy Markdown
Member

This was superseded by #8088, based on your findings and ideas. Excellent job! 🔥

@alcuadrado alcuadrado closed this Mar 27, 2026
@Wodann Wodann deleted the perf/imports branch May 6, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changeset needed This PR doesn't require a changeset no docs needed This PR doesn't require links to documentation no peer bump needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants